/// <reference path="./Block.ts" />
/// <reference path="./Eventable.ts" />
/// <reference path="./Matrix.ts" />

namespace tetris {
    import BlockPoint = model.BlockPoint;

    const factory = BlockFactory.default;

    export class BlockController extends Eventable {
        private _next: Block | null = null;
        private _current: Block | null = null;
        private _matrix: Matrix;

        constructor(width: number, height: number) {
            super();
            this._matrix = new Matrix(width, height);
        }

        get current(): Block | null {
            return this._current;
        }

        set current(value: Block | null) {
            this._current = value;
            this.trigger("currentChanged", { block: value });
        }

        get next(): Block {
            return this._next || (this._next = factory.create());
        }

        set next(value: Block) {
            this._next = value;
            this.trigger("nextChanged", { block: value });
        }

        run() {
            this.next = factory.create();
            this.process();
        }

        restart() {
            this._matrix.reset();
            this.trigger("reset");
            this.current = null;
            this.render();
            this.process();
        }

        process() {
            if (!this.current) {
                this.processNextShape();
            } else {
                this.processFallDown();
            }
        }

        processNextShape() {
            const block = this.current = this.next;
            this.next = factory.create();
            this.render();
            this.current.setPosition(~~((this._matrix.width - block.width) / 2), -block.height);
            this.processFallDown();
        }

        processFallDown() {
            if (!this.current) {
                return;
            }
            const current = this.current;
            const blocks = current.fastenOffset(0, 1);
            if (!this._matrix.isPutable(blocks)) {
                this.fastenCurrent();
                return;
            }

            current.moveDown();
            this.render();
        }

        fastenCurrent() {
            if (!this.current) {
                return;
            }
            this.trigger("fastening");

            if (this.current.top < 0) {
                this.render();
                this.trigger("gameover");
                return;
            }

            this._matrix.addBlockPoints(this.current.fasten());
            this.current = null;

            const eraser = new Eraser({
                matrix: this._matrix,
                render: () => {
                    this.render(true);
                }
            });

            (async () => {
                if (eraser.check()) {
                    const rowsCount = eraser.rowsCount;
                    this.trigger("earsingRows", { rowsCount: rowsCount });
                    await eraser.erase();
                    this.trigger("erasedRows", { rowsCount: rowsCount });
                }

                this.render(true);
                this.trigger("fastened");
                this.render();
            })();

        }

        render(shouldFasten: boolean = false) {
            this.trigger("render", {
                shouldFasten: shouldFasten
            });
        }

        fastenMatrix(): Array<BlockPoint> {
            return this._matrix.fasten();
        }

        rotate() {
            const [width, height] = [this._matrix.width, this._matrix.height];
            const current = this.current;
            if (!current || !this._matrix.isPutable(current.fastenRotate(width, height))) {
                return;
            }

            current.rotate(width, height);
            this.render();
        }

        moveLeft() {
            const current = this.current;
            if (!current || !this._matrix.isPutable(current.fastenOffset(-1, 0))) {
                return;
            }

            current.moveLeft();
            this.render();
        }

        moveRight() {
            const width = this._matrix.width;
            const current = this.current;
            if (!current || !this._matrix.isPutable(current.fastenOffset(1, 0))) {
                return;
            }

            current.moveRight(width);
            this.render();
        }

        moveDown() {
            const height = this._matrix.height;
            const current = this.current;
            if (!current || !this._matrix.isPutable(current.fastenOffset(0, 1))) {
                return;
            }

            current.moveDown(height);
            this.render();
        }

        fallDownDirectly() {
            const current = this.current;
            if (!current) {
                return;
            }

            const left = current.left;
            const height = this._matrix.height;
            for (let top = current.top + 1; top <= height; top++) {
                if (!this._matrix.isPutable(current.fasten(left, top))) {
                    current.setPosition(left, top - 1);
                    break;
                }
            }

            this.render();
            this.fastenCurrent();
        }
    }
}
